home *** CD-ROM | disk | FTP | other *** search
-
- Hardware Diagnostics in Forth
- *****************************
-
-
- fine tools ...
- ==============
-
- Forth has been long used for large numbers of hardware projects, both
- commercial and private. RISC OS Forthmacs is exceptionally stable and
- needs only very few resources on the computer.
-
- Here's how you can write Forth programs to diagnose your hardware.
- The programs can range from very simple things like reading a register
- to very complicated things like performing communications protocols.
- Each higher level of tests can build on the lower levels. At any
- time, you can interactively execute any part of the test, without
- having to build a command interpreter into your program.
-
- This document describes how to do very simple things interactively.
- It is also possible to save your work in a file and to make sequences
- of tests run automatically.
-
-
- Warning
- =======
-
- The examples in the rest of this document assume that you are running
- on a stand-alone system, and that there really are I/O devices located
- at the addresses mentioned. If you are running RISC OS Forthmacs
- under RISC OS or Unix, both won't allow you to access device
- registers, so the examples in this paper will cause a core dump of the
- Forth process.
-
- In fact, most of these examples assume that you are trying to debug a
- board whose registers begin at virtual address (hex) 3340000. In RISC
- OS computers this would be a Simple Exp. card with fast access, this
- might not be right for your system, so please pick an address for your
- system where you know there is some device or memory.
-
-
- Getting started
- ===============
-
- The way to get Forth running on your machine is system-dependent, so
- we won't go into that topic here. We assume that you have already
- figured out how to get Forth going, and that it is prompting you with
- 'ok' .
-
-
- Poking at registers
- ===================
-
- The first hurdle to cross when debugging a new board is usually
- reading and writing the device registers. With most programming
- environments or monitors, if the register doesn't work, you are stuck.
- Not so with Forth. Suppose that you have a 32-bit register at address
- (hex) 3340000. You can read it with:
- 3340000 @ .
- @ says to read a 32-bit word from the preceding address. . says to
- print the result. @ is pronounced "fetch". In general, the @ symbol
- is pronounced 'fetch' in Forth terminology. There must be one or more
- spaces separating symbols!
-
- Note: The Forth parser is really simple; it just grabs the next
- sequence of non-blank characters (called a 'word' in the jargon) from
- the terminal and looks up that word in its internal dictionary. If it
- finds the word, it executes some associated code. If it doesn't find
- the word, it tries to parse the word as a number. If that fails, it
- complains.
-
- Note: @ accesses a 32-bit location, the address must be long-aligned.
- If your device is 8 bits wide, use C@ in this and future examples;
- also use C! instead of ! .
-
- There is a problem with 16-bit registers in ARM based computers, ARM
- cpus don't support word-wide 16-bit data access. You can use W@ and
- W! but both instructions use two byte-wide memory accesses, so this
- might not be what you wanted. Or you use 32-bit normal accesses and
- mask-off the other
-
- The most likely result of trying this exercise on a prototype
- peripheral board is that the board won't respond to the cycle, so the
- CPU will get a bus error, print a message, and abort back to Forth.
-
- On a working device, instead of getting an error, Forth will display
- the contents of the register you accessed.
-
-
- Scope Loops
- ===========
-
- No, you don't have to get out your assembly language reference manual
- and try to figure out how to poke in a tiny loop. Here's how to make
- a loop:
-
- 3340000 constant reg-addr
- : test begin reg-addr @ drop key? until ;
-
- This creates a loop which will repeatedly read a register at location
- 3340000. BEGIN ... KEY? UNTIL means to keep doing everything between
- the BEGIN and the KEY? until a key is typed on the keyboard. The DROP
- is needed to get rid of the value that was read from the register,
- which is left on a stack. That stack would eventually overflow if not
- for the DROP. The loop is called TEST , and it is a new command which
- you have just created, you could have called it anything you wanted,
- instead of TEST .
-
- Remember that there will be an address exception if the physical
- address hasn't been accepted by the MMU. Now you can try the loop.
- test
-
- In general, the way to execute a Forth command is by typing its name.
-
- So now the machine is sitting there banging away at your register.
- You can try to find a scope that still has some probes attached and
- figure out why your register isn't responding.
-
- It wasn't actually necessary to have given the loop a name. You could
- have just typed:
- begin reg-addr @ drop again
-
- This is different from almost all Forth dialects, RISC OS Forthmacs
- knows about temporary compilation and forgets about the compiled code
- afterwards.
-
- However, by giving the command a name, you save it away so you can use
- it later, just by typing the name. It's not saved on disk, just in
- memory, so if you reboot, the new command will be lost. It would be
- nice if you could save your work on disk, but in a lot of stand-alone
- debugging cases there is no disk on the machine. To learn how you can
- save your work, read the "Creating Stand-Alone Forths" chapter
-
-
- Writing to registers
- ====================
-
- Now that you can read your register, no doubt you want to write to it
- too.
- 1234 reg-addr !
- writes the 32-bit word 1234 (hex) to the address left by the word
- REG-ADDR (which we defined earlier). If you want to write a byte
- instead of a 32-bit word, use C!.
- reg-addr @ .
- reads back the register and prints the value, so you can verify that
- the write actually worked.
-
-
- Do Loops
- ========
-
- An obvious thing to do now is to write a bunch of different values to
- the register and see if they all work.
-
- : test-loop
- ffff 0 do
- i reg-addr ! ( write a value to the register )
- reg-addr @ ( read it back ) ( register value on stack )
- i <> ( see if the value read back is different from the one written )
- if ." Error - wrote " i . ." read " reg-addr @ . cr
- then
- loop ;
-
- The indentation is optional. If you were writing this test on-the-fly
- while sitting in the lab, you would probably not bother with
- indentation. Similarly, everything inside parentheses is a comment
- and may be omitted. When you are writing Forth programs to save
- (presumably using a Unix editor), please don't omit the comments or
- the indentation, because that would make your work hard to understand
- later.
-
- How does this test-loop work? Let's go over it line-by-line. 'ffff 0'
- are the arguments to the DO ... LOOP construct. The loop starts at 0
- and ends when the loop index reaches (hex) ffff. The last time
- through the loop, the index has the value (hex) fffe. The firs thing
- inside the loop is 'i reg-addr !' . Previously we used the literal
- number '1234' as the value to store into location reg-addr. This time
- we use the loop index I . The loop index is 'always' called I . If
- you use nested loops, the index of the next outer loop is called J .
-
- The next thing we do inside the loop is read back the register.
- Previously we printed the value as soon as we read it; this time we
- will let the program look at and decide if it's okay. But where is
- the value kept? It's on the stack, just like on an HP calculator. In
- fact almost every operator in Forth takes its operands from the stack
- and leaves its results on the same stack. I will assume that this
- concept is familiar to you; if it isn't, let me know and I will either
- explain it to you or loan you a book which does so. Anyway, the
- register value is now sitting on the stack. The next thing we do is
- compare that value to the loop index I . The operator <> (not-equal)
- compares the top two things on the stack and leaves true if they are
- not equal or false if they are equal.
-
- If the numbers are equal, all is well. If they are different, we need
- to print an error message. That is where the IF ... THEN construct
- comes in. Here is the strange part: The stuff you want to do if the
- condition is true goes BETWEEN IF and THEN, not after THEN as one
- would expect. This is unfortunate, but it is not the end of the
- world. The condition that is tested comes BEFORE the IF; in this case
- the condition is the true/false value left on the stack by the <>
- operator. If this seems strange to you, consider that it is very
- simple, yet completely general. It is also possible to specify an
- ELSE clause (details later).
-
- The only thing remaining for this test-loop is to describe how the
- error message is printed. The construct '." ... "' , pronounced
- "dot-quote, prints whatever is inside the quotes. The first space
- after the first quote is mandatory and is not printed. Any subsequent
- spaces before the next quote are part of the string and are printed.
- Next we print the loop index with I . . As you have probably guessed,
- . just means print whatever number is on the stack. Next we print
- another string, followed by the value read back from the register.
- Finally, CR prints a carriage-return and linefeed.
-
-
- Extensibility
- =============
-
- Earlier we saw how to make a word called 'test' which could then be
- executed by typing its name. Once you have made a word, you can then
- use it as part of another word, thus building on top of your previous
- work. For example, suppose that there is a dma address register on
- your board, and that its address is (hex) 3340804. You can define a
- word to store a value into that register as follows:
- : dma! 3340804 ! ;
- This defines a new word called DMA! which takes an argument and stores
- it into the prescribed location. This word can be used as:
- f00000 dma!
- which will store f00000 into the dma register. Now, suppose that as
- part of a test, you need to automatically set the dma register. You
- can use your word dma! as part of another word.
- : init-dma f00000 dma! ;
- This is a trivial example, but it serves to illustrate the style of
- building up your application in small incremental steps. Don't
- hesitate to build words which only have a few components; the overhead
- of calling a word from one at higher level is quite small, and the
- advantages of small words are many (readability, ease of debugging,
- possibility of reuse).
-
-
- Variables
- =========
-
- Define a variable with
- variable foo
- The new variable FOO has space for a 32-bit word. Put a number in the
- variable with:
- 129876 foo !
- and get it back with
- foo @
- The number to be stored is taken from the stack, and the number
- fetched is left on the stack. When you typed the 129876, that number
- was actually left on the stack, and FOO ! picked it up and put in the
- variable foo. FOO @ retrieved it from the variable and returned it to
- the stack.
-
-
- Constants
- =========
-
- A constant is a symbolic name for a number. In other words, when you
- type the name of a constant, it just leaves its number on the stack.
- One way of making a constant is the obvious:
- : mem-base 100000 ;
- Now the word mem-base is equivalent to the number 100000. A slightly
- more efficient form of this is:
- 100000 constant mem-base
- A word defined with CONSTANT will execute somewhat faster than one
- defined the other way (but you would probably never notice the
- difference).
-
-
- C Language Analogies
- ====================
-
- C Forth
- while( condition ) { BEGIN condition WHILE
- loop-body loop-body
- } REPEAT
- do { BEGIN
- loop-body loop-body
- until ( condition ) condition UNTIL
- for( i=start_value; end_value
- i<end_value; start_value
- i += increment ) { DO
- loop-body loop-body
- } increment +LOOP
- for( i=start value; end value
- i<end value; start value
- i++ ) { DO
- loop-body loop-body
- } LOOP
- if ( condition ) { condition
- true_clause IF true_clause
- } else { ELSE false_clause
- false_clause THEN
- }
- if ( condition ) { condition
- true_clause IF true_clause
- } THEN
-
- Forth Notes:
-
- "condition" is any sequence of Forth words that has the effect of
- leaving a number on the stack. If the number the stack is 0, the
- condition value is false, otherwise it is true.
-
- Within a do loop, the word I will put the loop index on the stack.
-
-
- One More Thing ...
- ==================
-
- You may want to do a scope loop which can be easily interrupted. You
- can always abort back to RISC OS Forthmacs with Shift-Ctrl-F12. A
- nicer way, however, is the following:
- : scope-loop begin 1234 reg-addr ! key? until ;
- This word will continuously write 1234 to location 'reg-addr' until
- you type any key. The word KEY? returns true (which happens to be
- equal to -1) if a key has been depressed, and false (0) if not.
-
-
- Other Wonderful Features
- ========================
-
- Forth includes, among other things, a resident assembler, so you can
- write little bits of assembly code if you need to. It has a built-in
- visual line editor, so you can edit command lines as you type them.
- There are packages for defining structures and bit fields, similar to
- C. A built-in decompiler allows you to interactively decompile any
- Forth word that you have previously defined. Try typing SEE followed
- by the name of any Forth command, or any Forth word you have already
- defined.
-
-
- Line Editing
- ============
-
- While you are typing a Forth command line, you can move around in the
- line and edit it. Have a look at the chapter
- TYPING FORTH COMMAND LINES .
-
-